#include "Wt.h"

/* Create a custom command that operates on a table */

void WtCreateTableCmd(Tcl_Interp *interp, const char *cmdName, Tcl_Obj *tbl)
{
  Tcl_CreateObjCommand(interp, cmdName, WtNamedTableCmd,
                       (ClientData)tbl, NULL);
}

/* The custom table command procedure */

int WtNamedTableCmd(ClientData clientData, Tcl_Interp *interp,
                     int objc, Tcl_Obj *const objv[])
{
  int ret;
  Tcl_Obj *tbl = (Tcl_Obj *)clientData;
  WtTblMethodHelper(clientData, interp, objc, objv, &ret,
    tbl, 0, 1);
  return ret;
}

/* Create a Tcl array from a table object */

int WtTableToArray(Tcl_Interp *interp, Tcl_Obj *table,
                 Tcl_Obj *arrName, int multiVals)
{
  int ok = 1;
  Tcl_HashTable *hashTable = WtGetTableMap(table);
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *key, *vals, *val;
  Tcl_ObjType *listType = Tcl_GetObjType("list");

  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {    
    key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
    vals = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (multiVals) {
      if (!Tcl_ObjSetVar2(interp, arrName, key, vals, TCL_LEAVE_ERR_MSG)) {
        ok = 0;
        break;
      }
    } else {
      val = WtGetFirstListElement(vals);
      if (!Tcl_ObjSetVar2(interp, arrName, key, val, TCL_LEAVE_ERR_MSG)) {
        ok = 0;
        break;
      }
    }
    ent = Tcl_NextHashEntry(&search);
  }

  return ok;
}

Tcl_Obj *WtTblCmdGetTable(Tcl_Interp *interp, Tcl_Obj *obj, int *created, Tcl_ObjType *type)
{
  Tcl_Obj *found;

  found = WtGetTableRef(obj, type, created, interp);

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, WtGetContext(),
          "Wtcl: WtTblCmdGetTable: found = 0x%x", found);

  if (!found) {
    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
      "Parameter is not a valid table object or does not have the correct "
      "table type.", NULL);
  }

  if (found && *created) {
    Tcl_IncrRefCount(found);
  }

  return found;
}

/* Helper to manage table objects. */

WtTblCmdStatus WtTblCmdHelper(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[], int *tclRet, Tcl_ObjType *type,
    int readOnly, int appendUsage)
{
  int status = WT_TBL_CMD_DONE, ret = TCL_ERROR, error = 0;
  int listLen, created = 0;
  WtContext *w;
  char *subCmd;
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp), *tbl, *obj, *list;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc < 2) {

    if (appendUsage) {
      WtTblCmdUsageError(interp, objv[0], readOnly);
    }

    return WT_TBL_CMD_MISSING;

  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "create")) {

    /* create command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      if (type == &WtStringTableType) {
        obj = WtNewStringTableObj(NULL);
      } else {
        obj = WtNewTableObj(NULL);
      }
      Tcl_SetObjResult(interp, obj);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "count")) {

    /* count command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(WtTableSize(tbl)));
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "exists")) {

    /* exists command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " table key", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        obj = WtTableGet(tbl, objv[3]);
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(obj != NULL));
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "get")) {

    /* get command */

    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, WtGetContext(),
            "WtTableGet (tbl = 0x%x, key = 0x%x)");

    if (objc < 4 || objc > 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ",
        WtToString(objv[1]), " table key ?defaultValue?", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        obj = WtTableGet(tbl, objv[3]);
        if (obj) {
          Tcl_SetObjResult(interp, obj);
          ret = TCL_OK;
        } else if (objc > 4) {
          Tcl_SetObjResult(interp, objv[4]);
          ret = TCL_OK;
        } else {
          Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
             WtToString(objv[3]), "\" exists, and no default value was specified.",
            NULL);
        }
      }
    }

  } else if (!strcmp(subCmd, "set") && !readOnly) {

    /* set command */

    if (objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key value", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        WtTableSet(tbl, objv[3], objv[4]);
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "setDefault") && !readOnly) {

    /* setDefault command */

    if (objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " table key value", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        obj = WtTableGet(tbl, objv[3]);
        if (obj) {
          Tcl_SetObjResult(interp, obj);
        } else {
          WtTableSet(tbl, objv[3], objv[4]);
          Tcl_SetObjResult(interp, objv[4]);
        }
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "add") && !readOnly) {

    /* add command */

    if (objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key value", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        WtTableAdd(tbl, objv[3], objv[4]);
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "getAll")) {

    /* getAll command */

    if (objc != 4 && objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key ?defaultValue?", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        list = WtTableGetAll(tbl, objv[3]);
        if (list) {
          Tcl_SetObjResult(interp, list);
          ret = TCL_OK;
        } else if (objc > 4) {
          Tcl_SetObjResult(interp, objv[4]);
          ret = TCL_OK;
        } else {
          Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
            WtToString(objv[3]), "\" exists, and no default value was specified.",
            NULL);
        }
      }
    }

  } else if (!strcmp(subCmd, "setAll") && !readOnly) {

    /* setAll command */

    if (objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key list", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        if (Tcl_ListObjLength(interp, objv[4], &listLen) != TCL_OK) {
          Tcl_AppendStringsToObj(interpRes,
            "Value is not a list. Usage: ",
            WtToString(objv[0]), " ", WtToString(objv[1]),
            " table key list", NULL);
        } else {
          WtTableSetAll(tbl, objv[3], objv[4]);
          Tcl_SetObjResult(interp, objv[4]);
          ret = TCL_OK;
        }
      }
    }

  } else if (!strcmp(subCmd, "setAllDefault") && !readOnly) {

    /* setAllDefault command */

    if (objc != 5) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key list", NULL);
    } else {
      if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
        if (Tcl_ListObjLength(interp, objv[4], &listLen) != TCL_OK) {
          Tcl_AppendStringsToObj(interpRes,
            "Value is not a list. Usage: ",
            WtToString(objv[0]), " ", WtToString(objv[1]),
            " table key list", NULL);
        } else {
          list = WtTableGetAll(tbl, objv[3]);
          if (list) {
            Tcl_SetObjResult(interp, list);
          } else {
            WtTableSetAll(tbl, objv[3], objv[4]);
            Tcl_SetObjResult(interp, objv[4]);
          }
          ret = TCL_OK;
        }
      }
    }

  } else if (!strcmp(subCmd, "addList") && !readOnly) {

    /* addList command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key list", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      if (Tcl_ListObjLength(interp, objv[4], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]),
          " table key list", NULL);
      } else {
        WtTableAddList(tbl, objv[3], objv[4]);
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "keys")) {

    /* keys command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      Tcl_SetObjResult(interp, WtTableKeys(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "values")) {

    /* values command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      Tcl_SetObjResult(interp, WtTableValues(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "allValues")) {

    /* allValues command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      Tcl_SetObjResult(interp, WtTableValues(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "remove") && !readOnly) {

    /* remove command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table key", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      WtTableRemove(tbl, objv[3]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "removeList") && !readOnly) {

    /* removeList command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table list", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Argument is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]), " table list", NULL);
      } else {
        WtTableRemoveList(tbl, objv[3]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "clear") && !readOnly) {

    /* clear command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      WtClearTable(tbl);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "array")) {

    /* array command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table arrayName", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      if (WtTableToArray(interp, tbl, objv[3], 0)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "listArray")) {

    /* listArray command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table arrayName", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      if (WtTableToArray(interp, tbl, objv[3], 1)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "copy")) {

    /* copy command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " table", NULL);
    } else if (tbl = WtTblCmdGetTable(interp, objv[2], &created, type)) {
      Tcl_SetObjResult(interp, Tcl_DuplicateObj(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "createCommand")) {

    /* createCommand command */

    if (objc != 3 && objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " name ?table?",
        NULL);
    } else {
      if (objc == 3) {
        if (type == &WtStringTableType) {
          obj = WtNewStringTableObj(WtToString(objv[2]));
        } else {
          obj = WtNewTableObj(WtToString(objv[2]));
        }
      } else if (objc == 4) {
        obj = WtTblCmdGetTable(interp, objv[3], &created, type);
      }
      if (obj) {
        WtCreateTableCmd(interp, WtToString(objv[2]), obj);
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      }
    }

  } else {

    /* unknown command */

    status = WT_TBL_CMD_NOT_FOUND;

    if (appendUsage) {
      WtTblCmdUsageError(interp, objv[0], readOnly);
    }

  }

  *tclRet = ret;

  if (created && obj) {
    Tcl_DecrRefCount(obj);
  }

  return status;
}

void WtTblCmdUsageError(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be one of:\n\n", NULL);
  WtTblCmdAppendUsage(interp, cmd, readOnly);
}

void WtTblCmdAppendUsage(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);

  if (readOnly) {
    Tcl_AppendStringsToObj(res,
      cmdStr, " create\n",
      cmdStr, " count\n",
      cmdStr, " exists table key\n",
      cmdStr, " get table key ?defaultValue?\n",
      cmdStr, " keys table\n",
      cmdStr, " values table\n",
      cmdStr, " array table arrayName\n",
      cmdStr, " copy table\n",
      cmdStr, " getAll table key ?defaultValue?\n",
      cmdStr, " allValues table\n",
      cmdStr, " listArray table arrayName\n",
      NULL);
  } else {
    Tcl_AppendStringsToObj(res,
      cmdStr, " create\n",
      cmdStr, " count\n",
      cmdStr, " exists table key\n",
      cmdStr, " get table key ?defaultValue?\n",
      cmdStr, " set table key value\n",
      cmdStr, " add table key value\n",
      cmdStr, " setDefault table key value\n",
      cmdStr, " keys table\n",
      cmdStr, " values table\n",
      cmdStr, " array table arrayName\n",
      cmdStr, " remove table key\n",
      cmdStr, " removeList table keys\n",
      cmdStr, " copy table\n",
      cmdStr, " clear table\n",
      cmdStr, " getAll table key ?defaultValue?\n",
      cmdStr, " setAll table key list\n",
      cmdStr, " addList table key list\n",
      cmdStr, " setAllDefault table key list\n",
      cmdStr, " allValues table\n",
      cmdStr, " listArray table arrayName\n",
      cmdStr, " createCommand name\n",
      NULL);
  }
}

int WtTableCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtTblCmdHelper(clientData, interp, objc, objv, &ret, NULL, 0, 1);
  return ret;
}

int WtStringTableCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtTblCmdHelper(clientData, interp, objc, objv, &ret,
    &WtStringTableType, 0, 1);
  return ret;
}

/* Helper to manage table objects. Implements the "object method args"
   command style. */

WtTblCmdStatus WtTblMethodHelper(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[], int *tclRet, Tcl_Obj *tbl,
    int readOnly, int appendUsage)
{
  int status = WT_TBL_CMD_DONE, ret = TCL_ERROR, error = 0;
  int listLen;
  WtContext *w;
  char *subCmd;
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp), *obj, *list;

  w = (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);

  if (objc < 2) {

    if (appendUsage) {
      WtTblMethodUsageError(interp, objv[0], readOnly);
    }

    return WT_TBL_CMD_MISSING;

  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "count")) {

    /* count command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      Tcl_SetObjResult(interp, Tcl_NewIntObj(WtTableSize(tbl)));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "exists")) {

    /* exists command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " key", NULL);
    } else {
      obj = WtTableGet(tbl, objv[2]);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(obj != NULL));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "get")) {

    /* get command */

    if (objc < 3 || objc > 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ",
        WtToString(objv[1]), " key ?defaultValue?", NULL);
    } else {
      obj = WtTableGet(tbl, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
          WtToString(objv[2]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "set") && !readOnly) {

    /* set command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key value", NULL);
    } else {
      WtTableSet(tbl, objv[2], objv[3]);
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "setDefault") && !readOnly) {

    /* setDefault command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " key value", NULL);
    } else {
      obj = WtTableGet(tbl, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
      } else {
        WtTableSet(tbl, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
      }
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "add") && !readOnly) {

    /* add command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key value", NULL);
    } else {
      WtTableAdd(tbl, objv[2], objv[3]);
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "getAll")) {

    /* getAll command */

    if (objc < 3 || objc > 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key ?defaultValue?", NULL);
    } else {
      list = WtTableGetAll(tbl, objv[2]);
      if (list) {
        Tcl_SetObjResult(interp, list);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendStringsToObj(interpRes, "No entry with key \"",
          WtToString(objv[2]), "\" exists, and no default value was specified.",
          NULL);
      }
    }

  } else if (!strcmp(subCmd, "setAll") && !readOnly) {

    /* setAll command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key list", NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]),
          " key list", NULL);
      } else {
        WtTableSetAll(tbl, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "setAllDefault") && !readOnly) {

    /* setAllDefault command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key list", NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]),
          " key list", NULL);
      } else {
        list = WtTableGetAll(tbl, objv[2]);
        if (list) {
          Tcl_SetObjResult(interp, list);
        } else {
          WtTableSetAll(tbl, objv[2], objv[3]);
          Tcl_SetObjResult(interp, objv[3]);
        }
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "addList") && !readOnly) {

    /* addList command */

    if (objc != 4) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " key list", NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[3], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Value is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]),
          " key list", NULL);
      } else {
        WtTableAddList(tbl, objv[2], objv[3]);
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "keys")) {

    /* keys command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      Tcl_SetObjResult(interp, WtTableKeys(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "values")) {

    /* values command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      Tcl_SetObjResult(interp, WtTableValues(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "allValues")) {

    /* allValues command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      Tcl_SetObjResult(interp, WtTableValues(tbl));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "remove") && !readOnly) {

    /* remove command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
    } else {
      WtTableRemove(tbl, objv[2]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "removeList") && !readOnly) {

    /* removeList command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
    } else {
      if (Tcl_ListObjLength(interp, objv[2], &listLen) != TCL_OK) {
        Tcl_AppendStringsToObj(interpRes,
          "Argument is not a list. Usage: ",
          WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
      } else {
        WtTableRemoveList(tbl, objv[2]);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "clear") && !readOnly) {

    /* clear command */

    WtClearTable(tbl);
    ret = TCL_OK;

  } else if (!strcmp(subCmd, "array")) {

    /* array command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " arrayName",
        NULL);
    } else {
      if (WtTableToArray(interp, tbl, objv[2], 0)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "listArray")) {

    /* listArray command */

    if (objc != 3) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        "arrayName", NULL);
    } else {
      if (WtTableToArray(interp, tbl, objv[2], 1)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "table")) {

    /* table command */

    if (objc != 2) {
      Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be: ",
        WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
    } else {
      Tcl_SetObjResult(interp, tbl);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "copy")) {

    /* copy command */

    Tcl_SetObjResult(interp, Tcl_DuplicateObj(tbl));
    ret = TCL_OK;

  } else {

    /* unknown command */

    status = WT_TBL_CMD_NOT_FOUND;

    if (appendUsage) {
      WtTblMethodUsageError(interp, objv[0], readOnly);
    }

  }

  *tclRet = ret;

  return status;
}

void WtTblMethodUsageError(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_AppendStringsToObj(interpRes, "Incorrect usage. Should be one of:\n\n", NULL);
  WtTblMethodAppendUsage(interp, cmd, readOnly);
}

void WtTblMethodAppendUsage(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);

  if (readOnly) {
    Tcl_AppendStringsToObj(res,
      cmdStr, " count\n",
      cmdStr, " exists key\n",
      cmdStr, " get key ?defaultValue?\n",
      cmdStr, " keys\n",
      cmdStr, " values\n",
      cmdStr, " array arrayName\n",
      cmdStr, " table\n",
      cmdStr, " copy\n",
      cmdStr, " getAll key ?defaultValue?\n",
      cmdStr, " allValues\n",
      cmdStr, " listArray arrayName\n",
      NULL);
  } else {
    Tcl_AppendStringsToObj(res,
      cmdStr, " count\n",
      cmdStr, " exists key\n",
      cmdStr, " get key ?defaultValue?\n",
      cmdStr, " set key value\n",
      cmdStr, " add key value\n",
      cmdStr, " setDefault key value\n",
      cmdStr, " keys\n",
      cmdStr, " values\n",
      cmdStr, " remove key\n",
      cmdStr, " removeList keys\n",
      cmdStr, " array arrayName\n",
      cmdStr, " table\n",
      cmdStr, " copy\n",
      cmdStr, " clear\n",
      cmdStr, " getAll key ?defaultValue?\n",
      cmdStr, " setAll key list\n",
      cmdStr, " addList key list\n",
      cmdStr, " setAllDefault key list\n",
      cmdStr, " allValues\n",
      cmdStr, " listArray arrayName\n",
      NULL);
  }
}

void WtInitTableCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "table", WtTableCmd, NULL, NULL);
  Tcl_CreateObjCommand(interp, "strtable", WtStringTableCmd, NULL, NULL);
}
